-module (flatula_server).

-export ([ start_link/0,
	   table_pids/1 ]).

-behaviour (gen_server).
-export ([ init/1,
	   handle_call/3,
	   handle_cast/2,
	   handle_info/2,
	   terminate/2,
	   code_change/3 ]).

%
% public
%

start_link () ->
  gen_server:start_link ({ local, ?MODULE }, ?MODULE, [], []).

table_pids (Table) ->
  case ets:lookup (?MODULE, Table) of
    [ { Table, Pids, _Owners } ] -> Pids;
    [] -> erlang:error ({ badarg, Table })
  end.

%
% gen_server callbacks
%

init ([]) ->
  process_flag (trap_exit, true),
  ets:new (?MODULE, [ set, public, named_table ]),
  { ok, [] }.

handle_call ({ open, Table, Options, Owner }, _From, State) ->
  Reply =
    case ets:lookup (?MODULE, Table) of
      [ { Table, Pids, Owners } ] ->
	ets:insert (?MODULE, { Table, Pids, [ Owner | Owners ] }),
	{ ok, Table };
      [] ->
	case start_table (Options) of
	  { ok, Pids } ->
	    ets:insert (?MODULE, { Table, Pids, [ Owner ] }),
	    { ok, Table };
	  Error ->
	    Error
	end
    end,
  { reply, Reply, State };

handle_call ({ close, Table, Owner }, _From, State) ->
  case ets:lookup (?MODULE, Table) of
    [ { Table, Pids, Owners } ] ->
      case lists:delete (Owner, Owners) of
	[] ->
	  lists:foreach (fun (Pid) -> gen_server:call (Pid, close) end, Pids),
	  ets:delete (?MODULE, Table);
	NewOwners ->
	  ets:insert (?MODULE, { Table, Pids, NewOwners })
      end;
    [] ->
      ok
  end,
  { reply, ok, State };

handle_call (_Request, _From, State) ->
  { noreply, State }.

handle_cast (_Request, State) ->
  { noreply, State }.

handle_info (_Msg, State) ->
  { noreply, State }.

terminate (_Reason, _State) ->
  ok.

code_change ({ down, Vsn }, State, _Extra) when Vsn < '0.1.0' ->
  % downgrayedd
  lists:foreach (fun
		   ({ Table, [ Pid | Pids ], Owners }) when is_pid (Pid) ->
		     case Pids of
		       [] ->
			 ok;
		       [_|_] ->
			 error_logger:info_msg ("flatula table ~w with ~b "
						"partitions being downgraded "
						"-- data will be lost!",
						[Table, length (Pids)])
		     end,
		     ets:insert (?MODULE, { Table, Pid, Owners });
		   (Entry) ->
		     error_logger:info_msg ("skipping unknown entry ~w "
					    "in ~w table while downgrading "
					    "to version ~w",
					    [ Entry, ?MODULE, Vsn ]),
		     ok
		 end,
		 ets:tab2list (?MODULE)),
  { ok, State };
code_change (OldVsn, State, _Extra) when is_atom (OldVsn), OldVsn < '0.1.0' ->
  % upgrayedd
  lists:foreach (fun
		   ({ Table, Pid, Owners }) when is_pid (Pid) ->
		     ets:insert (?MODULE, { Table, [Pid], Owners });
		   (Entry) ->
		     error_logger:info_msg ("skipping unknown entry ~w "
					    "in ~w table while upgrading "
					    "from version ~w",
					    [ Entry, ?MODULE, OldVsn ]),
		     ok
		 end,
		 ets:tab2list (?MODULE)),
  { ok, State };
code_change (_OldVsn, State, _Extra) ->
  { ok, State }.

%
% private
%

start_table (Options) ->
  N = proplists:get_value (n_partitions, Options, 1),
  BasePath = proplists:get_value (base_path, Options),
  start_table (BasePath, proplists:delete (base_path, Options), N, []).

start_table (BasePath, Options, N, Pids) when N > 0 ->
  ChildOptions = [ { base_path, base_path (N, BasePath) } | Options ],
  case supervisor:start_child (flatula_table_supervisor, [ChildOptions]) of
    { ok, Pid } ->
      start_table (BasePath, Options, N - 1, [ Pid | Pids ]);
    Error ->
      lists:foreach (fun (Pid) -> gen_server:call (Pid, close) end, Pids),
      { error, Error }
  end;
start_table (_BasePath, _Options, 0, Pids) ->
  { ok, Pids }.

base_path (1, BasePath) ->
  BasePath;
base_path (N, BasePath) when N > 1 ->
  BasePath ++ [ $- | integer_to_list (N) ].
